package gaussianelimination;

import java.util.ArrayList;

/**
 *
 * @author PavelsKletnojs
 */
public class GaussianElimination {
    private double[][] M;
    private  double[] X;
    public double euclideanNormX;
    public double euclideanNormB;
    
    

    public GaussianElimination(double[][] matrix) {
	this.M = matrix;	
    }
    public GaussianElimination(ArrayList<ArrayList<Double>> matrix){
	this.M = new double[ matrix.size() ][ matrix.get(0).size() ];
	for(int i = 0; i<matrix.size(); i++){
	    for(int j = 0; j<matrix.get(0).size(); j++){
		M[i][j] = matrix.get(i).get(j);
	    }
	}
    }
    
    public void set1percentError(){
	int size = M.length;
	int cell = 0;
	for(int i = 1; i<M.length; i++){
	    if(M[cell][size] < M[i][size])
		cell = i;
	}
//	M[cell][size] *= 1.01;
	M[cell][size] += M[cell][size]/100;
    }
    
    
    private void calculateEuclideanNormX(){
	double euclideanNorm = .0;
	for(double x : X){
	    euclideanNorm += (x*x);
	}
	euclideanNorm = Math.sqrt(euclideanNorm);
	this.euclideanNormX = euclideanNorm;
    }
    private void calculateEuclideanNormB(){
	double euclideanNorm = .0;
	for(int i = 0; i < M.length; i++){
	    euclideanNorm += (M[i][M.length]*M[i][M.length]);
	}
	euclideanNorm = Math.sqrt(euclideanNorm);
	this.euclideanNormB = euclideanNorm;
    }
        
    private void matrixRowSubstraction (int cell, boolean showSteps){
	if(0 == M[cell][cell]){
	    int i = cell;
	    while(i<M.length-1 && 0 == M[i][cell]){
		i++;
	    }	    
	    
	    for(int j = 0; j<M[0].length; j++){
		M[cell][j] -= M[i][j];
	    }
	    if(showSteps){
		System.out.println("Leading element is 0!");
		System.out.println("Row " + (i+1) + " substracted from row " + (cell+1) + ":");
	    	printMatrix();
	    }
	}
    }

    public void printMatrix(){
	for(int i = 0; i<M.length; i++){
	    for(int j = 0; j<M[0].length; j++){
		System.out.print(M[i][j] + " ");
	    }
	    System.out.println();
	}
	System.out.println();
    }
    
    public void printResultVector(){
	System.out.println("Result X array:");
	for(double a : X)
	    System.out.print(a + " ");
	System.out.println("\n");
    }
    
    public ArrayList<Double> getResultVector(){
	// Result vector starts from lower degree
	ArrayList<Double> result = new ArrayList<Double>();
	for(int i = 0; i<X.length; i++)
	    result.add(X[i]);
	return result;
    }    
    public ArrayList<Double> getResultPoly(){
	// Result vector starts from lower degree
	ArrayList<Double> result = new ArrayList<Double>();
	for(int i = X.length-1; i>=0; i--)
	    result.add(X[i]);
	return result;
    }
    
    public void runGaussianElimination(boolean showSteps){
	calculateEuclideanNormB();
	
	if(showSteps){
	    System.out.println("Initial matrix:");
	    printMatrix();
	    System.out.println("Forward:");
	}
	
	for(int k = 0; k < M.length - 1; k++){
	    if (0 == M[k][k]){
		matrixRowSubstraction(k, showSteps);
	    }
	    for(int j = k+1;j < M.length; j ++){ 
		// Fill Collumn k with zeros
		double factor = M[j][k] / M[k][k];
		for(int i = k; i < M[0].length; i++){
		    // Recalculate Row j
		    M[j][i] = M[j][i] - M[k][i]*factor;
		}
	    }
	    
	    if(showSteps){
		System.out.println("Step " + (k+1) + ":");
		printMatrix();
	    }
	}
	
	if(showSteps)
	    System.out.println("Backward:");
	    
	for(int k = M.length-1; k>0; k--){
	    for(int j = k-1; j>=0; j--){
		double factor = M[j][k] / M[k][k];
		for(int i = k; i < M[0].length; i++){
		    M[j][i] = M[j][i] - M[k][i]*factor;
		}
	    }
	    
	    if(showSteps){
		System.out.println("Step " + (M.length-k) + ":");
		printMatrix();
	    }
	}
	
	// Create Diaganal Matrix with 1 in main diaganal
	X = new double[M.length];	
	for(int i = 0; i<M.length; i++){
	    M[i][M[0].length-1] = M[i][M[0].length-1] / M[i][i];
	    M[i][i] = 1;
	    X[i] = M[i][M[0].length-1];
	}
	
	if(showSteps){
	    System.out.println("Diagonal matrix:");
	    printMatrix();
	}
	
	calculateEuclideanNormX();
    }
}
